import java.util.*;

public class Dispatcher extends Thread {
   
   public final static int		statusNEW 	= 0;
   public final static int		statusWAIT  = 1;
   public final static int		statusRUN   = 2;
   public final static int 		statusPAUSE	= 3;
   public final static int 		statusSTOP	= 4;
   public final static int		statusDIE	= 9;
   public int currentStatus = statusNEW;

   public final static String		wordError = "!ERROR!";
   public final static String		wordSucc  = "SUCCESS";

   public final static String		userName    = "bob";	// Temp hack
										// Defines text for username/pass	

   CmdQueue		commandsRx;
   Command		currentCommand;

   SlaveUI		myUI;
   
   String		target;
   Logging		log;

   String		currentScript;
   Scripter		script;
   ScriptItem	sItem;
   int 		scriptStep;

   Stack		loopStack;
   int		loopItem;
   Integer		loopTemp;
   
   // Chutes
   HTTPOut[]	out;
   ReqQueue[]	chute;	
   int 		chuteCount = 4;
   int		useChute;

   // Users
   int		userCount;
   int		userStart;

   int		index;
				 
   boolean		timeWaitOn = false;
   long		waitTill;
   public final static int 	CLOCK_MULTI	 = 1000;
 
   boolean		syncOn = false;


   // helper threads
   public HTTPIn		masterRx;

   public Dispatcher(SlaveUI ui) {
	
	// initialize me
      myUI = ui;
	this.initialize();
	timeWaitOn = false;
	loopStack  = new Stack();
   }

   public void run() {
      
	myUI.putScreenLog("Dispatcher: Im up.");

      masterRx.start();

	// Dispatch loop
	while (currentStatus != statusDIE) {

	   // check command queue
	   if (commandsRx.isCommand()) {
		currentCommand = commandsRx.getCommand();

		// process command
		switch(currentCommand.getCommand()) {
		   case Command.cmdNULL:
			handleNull();			
			break;

		   case Command.cmdSTOP:
			handleStop();
			break;

		   case Command.cmdNEW:
			handleNew();
			break;

		   case Command.cmdPAUSE:
			handlePause();
			break;

		   case Command.cmdTARGET:
			handleTarget();	
			break;
		 
		   case Command.cmdGETLOG:
			handleGetLog();
			break;   

		   case Command.cmdGO:
			handleGo();
			break;   

		   case Command.cmdPING:
			handlePing();
			break;   

		   default:
			myUI.putScreenLog("Software detected fault Dispatch.run");
			log.post("Software detected fault Dispatch.run");
			break;
		}
         } // end if

	   if (currentStatus == statusRUN) {

	      if (timeWaitOn == true) {

			// see if timer expired
			if (log.getTiming() >= waitTill) timeWaitOn = false;

		} else {

			if (syncOn == true) {
			   // See if we can clear a sync
			   handleSync();

			} else {
		 	   // lets grab a script item, if we have any more
			   if (scriptStep < script.numberItems) {	
			   	handleScript();
			   } else {
				//the script is done.
				//log and change state.  (We will want some kind of sync here)
				log.post("DONE!!! Ending at "+log.getTiming());
				myUI.putScreenLog("!!! DONE.  Script has ended.");
				currentStatus = statusSTOP;
			   }
			}

		}

		// take a breather.  I may want to remove this.  But, it should keep this damned 
		// thread from sucking up soooo much CPU time when relatively idle.
		try {
		   this.sleep(100);
		} catch (Exception e) { };

	   }  else {

		// Idle this thread if nothing is in the command queue and nothing is running
		if (commandsRx.isCommand() == false) {
		   try {
			this.sleep(500);
		   } catch (InterruptedException e) {
		      // dont care
		   }
		} 
	   } // end if running

      } // end while

   }

   protected boolean initialize() {
      
	// Build helpers
	commandsRx = new CmdQueue(); 

	log  	     = new Logging();

	masterRx = new HTTPIn(true, commandsRx);  
  
	return true;
   }

   protected void handleNew() {
	String r;
	String i;

	log.post("RX: NEW Command");
	myUI.putScreenLog("RX: NEW Command");

	// Check against current state
	switch (currentStatus) {
	
	   case statusSTOP:
	   case statusNEW:
		script = new Scripter();
		i 	 = script.load(currentCommand.getStringParam());
	
		if (script.errors > 0) {
	  	   myUI.putScreenLog("!!! A NEW command REJECTED due to errors.");
		   myUI.putScreenLog("!!! >>> Processed.  "+script.errors+" errors found.");
	    	   r = formResponse(wordError, "NEW script has been rejected by slave.", i);
		   currentCommand.respond(r);
	 	   log.post("--> NEW Parse errors: " + script.errors);
		   break;
		}

		userStart  = currentCommand.getIntParam();
		chuteCount = script.chutes;
		userCount  = script.users;
		useChute   = 0;

		// report 
		log.clear();				// Clear the log here.	
 		currentScript = script.name;
		myUI.setScriptName(currentScript);  
		myUI.putScreenLog("!!! A NEW command accepted.");
		r = formResponse(wordSucc, "NEW script has been loaded on slave.", i);
		currentCommand.respond(r);

		log.post("--> NEW Script Name: " + currentScript);
		currentStatus = statusWAIT;

		break;		

	   default:
		myUI.putScreenLog("!!! >>> Rejecting NEW.  Script already running or pending.");
	   	r = formResponse(wordError, "Rejecting NEW.  Already running or pending",
		    "You must STOP the currently running/pending script before posting a NEW script\n");
	   	currentCommand.respond(r);
	  	log.post("--> NEW Rejected.");

	}
   }

   protected void handleStop() {
	String r;

	log.post("RX: STOP Command");
	myUI.putScreenLog("RX: STOP Command");

	// Check against current state
	switch (currentStatus) {
	
	   case statusWAIT:
	   case statusRUN:
	   case statusPAUSE:

		myUI.putScreenLog("!!! A STOP command received.  Script stopped.");
		r = formResponse(wordSucc, "Current script stopped.", 
					"The script has been stopped on the slave.");
		currentCommand.respond(r);	
		log.post("--> Script has been stopped.");

		currentStatus = statusSTOP;
		log.stopTiming();
		log.done();

		// Kill the chutes
		int i = 0;
		while (i < chuteCount) {
		   try {
		      out[i].stop();
		   } catch (Exception e) {
			// dont care
		   }
		   i++;
 		}	
		break;

	   default:
		myUI.putScreenLog("!!! >>> Rejecting STOP.  Script not running or pending.");
	   	r = formResponse(wordError, "Rejecting STOP.  Not running or pending",
		    "There is no script currently running or pending.\n");
	   	currentCommand.respond(r);
	  	log.post("--> STOP Rejected.");
	}
   }

   protected void handleNull() {
	String r;
	String i;

	myUI.putScreenLog("RX: >> NULL << Command");
	log.post("RX: >> NULL << Command");
	log.post("--> Ignored.");

	// report 
	myUI.putScreenLog("!!! Ingoring NULL.");
	r = formResponse(wordError, "Unrecognized command", 
				"I dont know what to do with that command.");
	currentCommand.respond(r);
   }

   protected void handleTarget() {
	String r;

	log.post("RX: TARGET Command");
	myUI.putScreenLog("RX: TARGET Command");

	// Check against current state
	switch (currentStatus) {
	
	   case statusSTOP:
	   case statusNEW:

		target = currentCommand.getStringParam();
		myUI.setTarget(target);

		// report 
		myUI.putScreenLog("!!! TARGET accepted for :"+target);
		log.post("--> Target accepted for :"+target);
		r = formResponse(wordSucc, "TARGET Accepted", 
				     "New target is now set for this slave.");
		currentCommand.respond(r);
		break;

	   default:
		myUI.putScreenLog("!!! >>> Rejecting TARGET.  Script is running or pending.");
	   	r = formResponse(wordError, "Rejecting TARGET.  Script running or pending",
		    "You cannot set a target while a script is running or pending.\n");
	   	currentCommand.respond(r);
	  	log.post("--> TARGET Rejected.");
	}
   }

   protected void handleGetLog() {
	String r;

	myUI.putScreenLog("RX: GETLOG Command");
	log.post("RX: GETLOG Command");

	// report 
	myUI.putScreenLog("!!! Log posted to master.");

	r = formResponse(wordSucc, "Current LOG", log.getLog());
	currentCommand.respond(r);
   }

   protected void handlePing() {
	String r;

	myUI.putScreenLog("RX: PING Command");
	//log.post("RX: PING Command");

	// report 
	r = formResponse(wordSucc, "PONG", "Status=" + currentStatus + "=");
	currentCommand.respond(r);
   }


   protected void handleGo() {
	String r;

	myUI.putScreenLog("RX: GO command");

	// Check against current state
	switch (currentStatus) {
	
	   case statusWAIT:
	   case statusSTOP:
		myUI.putScreenLog("!!! A GO command received.  Script running.");
		r = formResponse(wordSucc, "Current script running.", 
					"The script is now running from the start.");
		currentCommand.respond(r);
		log.clear();	
		log.startTiming();
		log.post("RX: GO Command");
		log.post("--> Script has been started.");
		
		// Build the chutes
		int i = 0;
		chute = new ReqQueue[chuteCount];
		out	= new HTTPOut[chuteCount];
		while (i < chuteCount) {
		   chute[i] = new ReqQueue();
		   chute[i].init();
		   out[i] = new HTTPOut();
               out[i].init(chute[i], log);
		   out[i].start();
		   i++;
 		}	
		useChute = 0;	

		currentStatus = statusRUN;

		// Start from beginning of script!
		scriptStep	  = 0;			
		while(loopStack.empty() != true) { loopTemp = (Integer) loopStack.pop(); }
		loopItem = ScriptItem.maxLoop;		// remove any pending loops

		break;

	   case statusPAUSE:

		myUI.putScreenLog("!!! A GO command received.  Script restarted from PAUSE.");
		r = formResponse(wordSucc, "Current script restarted.", 
					"The script is now running from a pause.");
		currentCommand.respond(r);
		log.resumeTiming();
		log.post("RX: GO Command");
		log.post("--> Script has been restarted from a pause.");

		currentStatus = statusRUN;
		break;

	   default:
		//log.post("RX: GO Command");
		myUI.putScreenLog("!!! >>> Rejecting GO.  Script not ready or already running.");
	   	r = formResponse(wordError, "Rejecting GO.  Already running or not ready.",
		    "A script is already running or one has not been loaded.\n");
	   	currentCommand.respond(r);
	  	log.post("--> GO Rejected.");
	}
   }

   protected void handlePause() {
	String r;

	myUI.putScreenLog("RX: PAUSE Command");
	log.post("RX: PAUSE Command");

	// Check command against state
	if (currentStatus != statusRUN) {
	   myUI.putScreenLog("!!! >>> Rejecting PAUSE.  Not currently running.");
	   r = formResponse(wordError, "Rejecting PAUSE.",
		 "No script is currently running.\n");
	   currentCommand.respond(r);
	   log.post("--> PAUSE Rejected.  Not running.");
	   return;
	}

	currentStatus = statusWAIT;
	log.pauseTiming();

	// report 
	myUI.putScreenLog("!!! PAUSE accepted. ");
	log.post("--> PAUSE Accepted:");
	r = formResponse(wordSucc, "PAUSE Accepted", "Script is now paused.");
	currentCommand.respond(r);
   }

   private String formResponse(String  title, String  note, String  content) {
      StringBuffer r = new StringBuffer();
	r.append("<HTML><HEAD><TITLE>"+title+"</TITLE></HEAD><BODY><H2>"+note);
	r.append("</H2><PRE>"+content+"</PRE></BODY></HTML>");
	return r.toString();
   }

   private void handleScript() {

	sItem = script.getItem(scriptStep);
	scriptStep++;
	//System.out.println("TOKEN: " + sItem.token); 
	switch(sItem.token) {

	   case ScriptItem.tokenPut:
		int    i;
		if (sItem.id == 0) {
		   i = useChute;
		   useChute++;
		   if (useChute >= chuteCount) useChute = 0;	
		} else {
		   i = (script.userList[sItem.id]);
		}
		put( sItem.text, sItem.id, i );
		break;

	   case ScriptItem.tokenIndexPut:
		if (index > userCount) {
		   myUI.putScreenLog("Software detected fault Dispatch.handleScript-INDEX out of bounds");
		   log.post("Software detected fault Dispatch.handleScript-INDEX Inc of bounds.  Used index = 1");
		   index = 1;
		}
		try {   
		   put( sItem.text, index, script.userList[index] );
		} catch (Exception e) {
		   System.out.println("INDEX OOB =  " + index);
		   myUI.putScreenLog("Software detected fault Dispatch.handleScript-INDEX out of bounds");
		   log.post("Software detected fault Dispatch.handleScript-INDEX out of bounds");
		}
		break;

	   case ScriptItem.tokenIndexWait:
		if (index > userCount) {
		   myUI.putScreenLog("Software detected fault Dispatch.handleScript-INDEX WAIT out of bounds");
		   log.post("Software detected fault Dispatch.handleScript-INDEX WAIT of bounds.  Used index = 1");
		   index = 1;
		}
		try {
		   chuteWait(index, (sItem.count * CLOCK_MULTI));
		} catch (Exception e) {
		   System.out.println("INDEX WAIT OOB =  " + index);
		   myUI.putScreenLog("Software detected fault Dispatch.handleScript-INDEX WAIT out of bounds");
		   log.post("Software detected fault Dispatch.handleScript-INDEX WAIT out of bounds");
		}
		break;
		
	   case ScriptItem.tokenWait:
		if (sItem.id == 0) {
		   // General Wait
		   timeWaitOn = true;
		   log.post("WAIT : Time now : "+log.getTiming());
   		   waitTill = log.getTiming() + (sItem.count * CLOCK_MULTI);
		   log.post("     : until "+waitTill);
		} else {
		   // Chute wait.  Follows a user
		   chuteWait(sItem.id, (sItem.count * CLOCK_MULTI));
		}
		break;

	   case ScriptItem.tokenLoop:
		
		// If we have any loops pending, push the loopItem onto a stack so
		// we can remember the loop count later.
		if (loopItem != ScriptItem.maxLoop) {
		   loopTemp = new Integer(loopItem);	
		   loopStack.push(loopTemp);
		}
		loopItem  = sItem.count;
		break;
		
	   case ScriptItem.tokenEndLoop:
		
		loopItem--;
		if (loopItem > 0) {
			scriptStep = sItem.count + 1;

		} else { 
			// Do we need to recover a outside loop?
			if (loopStack.empty() == false) {
			   loopTemp  = (Integer) loopStack.pop();
			   loopItem  = loopTemp.intValue(); 

			} else {
			   loopItem = ScriptItem.maxLoop;	// clear the pending loop (or, adding a new
									// loop will be nested.)
			}
		}
		break;

	   case ScriptItem.tokenEnd:
		log.post("END : Ending at "+log.getTiming());
		break;

	   case ScriptItem.tokenSync:
		log.post("SYNC: Starting at "+log.getTiming());
		syncOn = true;
		break;

	   case ScriptItem.tokenIndexSet:
		index = sItem.count;
		System.out.println("INDEX =  " + index);
		break;

	   case ScriptItem.tokenIndexInc:
		index++;
		break;

	   case ScriptItem.tokenName:
	   default:
		myUI.putScreenLog("Software detected fault Dispatch.handleScript-tokenDEFAULT");
		log.post("Software detected fault Dispatch.handleScript-tokenDEFAULT");
		break;

	}

   }
      
   private void handleSync() {

	int 		i	    = 0;
	int 		size	    = 0;
	int		idleClear = 0;

	while (i < chuteCount) {
  	   size = size + chute[i].size();
  	   if (out[i].idle == false) idleClear++;
	   i++;
 	}	
	if ((size == 0)&&(idleClear == 0)) {
	   log.post("SYNC:  Sync cleared.");
	   syncOn = false;	
	}

   }

   private void put(String  text, int  user, int  chuteNum) {
	StringBuffer s = new StringBuffer();
	Request	 r = new Request();
	int		 c = chuteNum - 1;    // The chute is one less as an array index;
	int		 rover = 0;
	int		 size  = text.length();
	char		 temp;

	// Lets chug through this dog and build the URL.  Replace any idChar with 
	// a username/password string.
	s.append("http://"+target);
	while (rover < size) {
	   temp = text.charAt(rover);
	   if (temp == ScriptItem.idChar) {
		s.append(userName + ((user-1)+userStart));
	   } else {
	      s.append(temp);
	   }
	   rover++;	
	}

	r.type = Request.reqPUT;
	r.text = s.toString();

//System.out.println("QUEUE " + c + ": PUT : " + r.text); 
//System.out.println("QUEUE " + c + ": PUT : " + text); 
	chute[c].put(r);
   }

   private void chuteWait(int  user, int  time) {
	Request	 r = new Request();
	int		 c;

	c 		= script.userList[user]-1; // The chute is one less as an array index;
	r.type 	= Request.reqWAIT;
	r.intParam	= time;

	System.out.println("QUEUE " + c + ": WAIT " + time); 
	chute[c].put(r);
   }


}     

